/*
问题1：avcodec_send_frame和avcodec_receive_packet如果在不同线程运行，相互调用时，需要加上互斥锁，以免发生异常

*/

/**
 *
 * 代码需要重构
 * 内存泄漏需要解决
 *
 * 采集和编码需要同时进行
 * 编码需要从软件改硬件
 *
 */
#include "DataEncoding.h"
#include"../Client/SendFrameClient.h"

DataEncoding::DataEncoding(SendFrameClient* sendFrameClt,AVCodecID id, int width, int height, int framerate, AVPixelFormat inputFigFormat, DataCollect *dataCollect)
    : id(id), width(width), height(height), framerate(framerate), inputFigFormat(inputFigFormat),
      sendFrameClt(sendFrameClt),
      dataCollect(dataCollect)
{
    pool = new HzThreadPool(10);
}

DataEncoding::DataEncoding()
{
}

DataEncoding::~DataEncoding()
{
}

int DataEncoding::init_encode_core()
{
    cout << "init encoder..." << endl;

    // 查找编码器   AV_CODEC_ID_H264
    codec = avcodec_find_encoder_by_name("h264_nvenc");
    //codec = avcodec_find_encoder(id);
    if(!codec){
        cout << "无法找到h264_nvenc编码器" << endl;
        codec = avcodec_find_encoder(id);
    }

    if (!codec)
    {
        cout << "无法找到H.264编码器" << endl;
        return -1;
    }

    // 1 分派上下文
    codecContext = avcodec_alloc_context3(codec);
    if (!codecContext)
    {
        cout << "无法分配编码器上下文" << endl;
        return -1;
    }

    // 2 设置编码器参数
    codecContext->profile = FF_PROFILE_H264_HIGH; // 表示使用H.264的高级别配置
    codecContext->bit_rate = 5 * 1000 * 1000;                    // 设置码率
    codecContext->width = width;                                 // 设置视频宽度
    codecContext->height = height;                               // 设置视频高度
    codecContext->time_base = (AVRational){1, framerate};        // 表示每个时间单位（帧）的持续时间是1/25秒，帧率为25
    codecContext->framerate = (AVRational){framerate, 1};        // 设置帧率
    codecContext->gop_size = 10;                                 // 设置关键帧间隔
    codecContext->max_b_frames = 0;                              // 设置I P帧之间的最大B帧数量
    codecContext->pix_fmt = inputFigFormat;                      // 设置输入图像为YUV420格式  AV_PIX_FMT_YUV420P

    if (codec->id == AV_CODEC_ID_H264)
    {
        av_opt_set(codecContext->priv_data, "preset", "slow", 0);
        //av_opt_set(codecContext->priv_data, "tune", "zerolatency", 0);
    }
    // 打开编码器
    if (avcodec_open2(codecContext, codec, nullptr) < 0)
    {
        cout << "无法打开编码器\n";
        return -1;
    }
    // 创建AVPacket并分配内存
    pkt = av_packet_alloc();
    if (!pkt)
    {
        cout << "创建AVPacket错误" << endl;
    }

    // 创建AVFrame并分配内存
    frame = av_frame_alloc();
    if (!frame)
    {
        cout << "无法分配AVFrame\n";
        return -1;
    }
    frame->width = codecContext->width;
    frame->height = codecContext->height;
    frame->format = codecContext->pix_fmt; // 设置像素格式
    if (av_frame_get_buffer(frame, 0) < 0)
    {
        cout << "无法分配帧缓冲区\n";
        return -1;
    }
    cout << "encoder init successfully!!" << endl;
    return 0;
}

int DataEncoding::release_encode_core()
{
    cout << "release encode core..." << endl;
    avcodec_free_context(&codecContext);
    av_frame_free(&frame);
    av_packet_free(&pkt);
    cout << "release encode core sucessfully!" << endl;
    return 0;
}

int DataEncoding::run()
{
    pool->Invoke();
    init_encode_core();
    // 1 新开线程将帧原始数据送入上下文
    std::thread thread1(&DataEncoding::start_encode, this);
    // 2 循环获取编码结果
    int i = 1;
    while (1)
    {

        int ret = this->recv_the_pkt();
        if (ret == 0)
        { //发送给控制端
            cout<<"sned id："<<i++<<endl;
            sendFrameClt->send_a_frame_to_server(new string((char*)pkt->data,pkt->size));
            //pool->DispatchWorker(new ThreadWorker(&DataEncoding::send_a_frame_to_server,this,new string((char*)pkt->data,pkt->size)));
        }
        else if (ret == -1)
        { // 读取发生错误
            release_encode_core();
            return -1;
        }
        else if (ret == AVERROR_EOF)
            break;
    }

    //告诉服务器停止
    //sendFrameClt->sendData("end",sendFrameClt->sock->s,sendFrameClt->serverAdd4);
    thread1.join();
    pool->Stop();
    release_encode_core();
    return 0;
}

void DataEncoding::start_encode()
{
    YuvBuffer *bufPtr;
    int i = 0;
    while (dataCollect->status)
    {
        if (yuvstreams.Size() == 0){
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
             continue;
        }
        // 1 开启获取缓冲区的yuv数据
        bufPtr = yuvstreams.Front();
        yuvstreams.Pop();
        // 2 存入结构
        memcpy(frame->data[0], bufPtr->y, bufPtr->ySize);
        memcpy(frame->data[1], bufPtr->u, bufPtr->uSize);
        memcpy(frame->data[2], bufPtr->v, bufPtr->vSize);
        delete bufPtr;
        // 3 发送到上下文
        frame->pts = i++;
        send_a_frame(false);
    }
    send_a_frame(true);
}

int DataEncoding::send_a_frame(bool flush)
{
    // 传入nullptr表示编码结束，此时刷新AVPacket中的缓存空间；否则继续编码frame
    readContextMutex.lock();
    int ret = avcodec_send_frame(codecContext, flush ? nullptr : frame);
    readContextMutex.unlock();
    switch (ret)
    {
    case 0:
        if (flush)
            //cout << "send frame to encoder with pts:" << frame->pts << endl;
            cout << "send frame end!!" << endl;
        break;
    case AVERROR(EAGAIN):
        // cout << "缓冲区已满，请先通过avcodec_receive_packet读出" << endl;
        break;
    default:
        cout << "erreo : send frame erro！" << endl;
        break;
    }
    return ret;
}

int DataEncoding::recv_the_pkt()
{
    // 1 尝试获取编码后的结果
    readContextMutex.lock();
    int ret = avcodec_receive_packet(codecContext, pkt);
    readContextMutex.unlock();
    // 2 判断获取结果
    switch (ret)
    {
    case AVERROR(EAGAIN): // "AVERROR_EOF:上下文中没有码流,输出结束；AVERROR(EAGAIN)：输出不可用\n";
        // cout<<"输出不可用"<<endl;
        break;
    case AVERROR_EOF:
        // cout<<"输出结束"<<endl;
        break;
    case 0: // 成功获取一帧编码后的码流
            // cout << "get encoded package with dts:" << pkt->dts << ",pts:" << pkt->pts << endl;
        break;
    default:
        cout << "erreo : get pkt fail!!" << endl;
        break;
    }
    return ret;
}
